package com.qfmy.web.app.service.impl.novel;

import com.github.xiaoymin.knife4j.core.util.CollectionUtils;
import com.qfmy.common.constant.RedisConstant;
import com.qfmy.common.exception.BaseException;
import com.qfmy.common.login.LoginUser;
import com.qfmy.common.login.LoginUserHolder;
import com.qfmy.common.result.ResultCodeEnum;
import com.qfmy.model.entity.novel.History;
import com.qfmy.model.entity.novel.Novel;
import com.qfmy.model.enums.NovelStatus;
import com.qfmy.model.vo.novel.NovelVo;
import com.qfmy.web.app.lucene.LuceneUtils;
import com.qfmy.web.app.mapper.novel.HistoryMapper;
import com.qfmy.web.app.mapper.novel.NovelMapper;
import com.qfmy.web.app.mapper.user.UserMapper;
import com.qfmy.web.app.service.conmon.SearchLogService;
import com.qfmy.web.app.service.novel.NovelService;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author 清风明月
 * @Package com.qfmy.web.app.service.impl.novel
 * @date 2025/10/31
 * @description 小说
 */

@Slf4j
@Service
public class NovelServiceImpl implements NovelService {

    /**
     * 注入mapper
     */
    @Autowired
    private NovelMapper novelMapper;

    /**
     * 注入用户mapper
     */
    @Autowired
    private UserMapper userMapper;

    /**
     * 注入浏览历史
     */
    @Autowired
    private HistoryMapper historyMapper;

    /**
     * 注入搜索日志
     */
    @Autowired
    private SearchLogService searchLogService;

    /**
     * 注入redisTemplate
     */
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    /**
     * 获取热门小说
     * @param categoryId
     * @return
     */
    @Override
    public List<NovelVo> getHotNovels(Integer categoryId) {
        // 入参校验
        if (categoryId == null) {
            log.warn("获取热门小说失败：categoryId为空");
            return Collections.emptyList();
        }
        // 缓存key
        String key = RedisConstant.APP_HOT_NOVEL_PREFIX + categoryId;

        try {
            // 直接查询缓存
            List<NovelVo> cachedNovels = (List<NovelVo>) redisTemplate.opsForValue().get(key);
            if (!CollectionUtils.isEmpty(cachedNovels)) {
                return cachedNovels;
            }

            // 锁粒度细化（按categoryId分锁，避免不同分类竞争同一把锁）
            String lockKey = RedisConstant.APP_HOT_NOVEL_PREFIX + "lock:" + categoryId;
            String lockValue = UUID.randomUUID().toString();
            boolean locked = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 3, TimeUnit.SECONDS));

            if (locked) {
                try {
                    // 双重检查缓存（防止释放锁前其他线程已写入缓存）
                    cachedNovels = (List<NovelVo>) redisTemplate.opsForValue().get(key);
                    if (!CollectionUtils.isEmpty(cachedNovels)) {
                        return cachedNovels;
                    }

                    // 数据库查询
                    List<Novel> novels = novelMapper.getHotNovels(categoryId);
                    cachedNovels = convertToVo(novels);

                    // 缓存写入（优化：空列表也缓存，但缩短过期时间，避免缓存穿透）
                    long expireHours = CollectionUtils.isEmpty(cachedNovels) ? 1 : 6;
                    redisTemplate.opsForValue().set(key, Objects.requireNonNull(cachedNovels), expireHours, TimeUnit.HOURS);

                    return cachedNovels;
                } finally {
                    //安全释放锁（使用Lua脚本，确保只删除自己持有的锁，避免误删）
                    String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                    DefaultRedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
                    redisTemplate.execute(script, Collections.singletonList(lockKey), lockValue);
                }
            } else {
                // 循环重试
                List<NovelVo> novelVos = getNovelVoList(cachedNovels, key);
                if (novelVos != null) return novelVos;
                List<Novel> novels = novelMapper.getHotNovels(categoryId);
                return convertToVo(novels);
            }
        } catch (Exception e) {
            // 最终降级：返回空列表（避免服务雪崩）
            return Collections.emptyList();
        }
    }

    /**
     * 获取小说排行
     * @return
     */
    @Override
    public List<NovelVo> getRank() {
        //创建缓存key
        String key = RedisConstant.APP_RANK_PREFIX;

        //获取缓存
        List<NovelVo> cachedNovels = (List<NovelVo>) redisTemplate.opsForValue().get(key);

        //缓存命中
        if (!CollectionUtils.isEmpty(cachedNovels)) {
            return cachedNovels;
        }

        //加锁
        String lockKey = RedisConstant.APP_RANK_PREFIX + "lock:" ;
        String lockValue = UUID.randomUUID().toString();
        boolean locked = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 3, TimeUnit.SECONDS));

        //判断锁是否获取成功
        if(locked)
        {
            try {
                //再次检测缓存
                cachedNovels = (List<NovelVo>) redisTemplate.opsForValue().get(key);
                if (!CollectionUtils.isEmpty(cachedNovels)) {
                    return cachedNovels;
                }

                //数据库查询
                List<Novel> novels = novelMapper.getRank();
                //转换vo
                cachedNovels = convertToVo(novels);

                // 缓存写入（优化：空列表也缓存，但缩短过期时间，避免缓存穿透）
                long expireHours = CollectionUtils.isEmpty(cachedNovels) ? 1 : 5;
                redisTemplate.opsForValue().set(key, Objects.requireNonNull(cachedNovels), expireHours, TimeUnit.HOURS);

                //返回
                return cachedNovels;
            } finally {
                //安全释放锁（使用Lua脚本，确保只删除自己持有的锁，避免误删）
                String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                DefaultRedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
                redisTemplate.execute(script, Collections.singletonList(lockKey), lockValue);
            }
        }else {
            List<NovelVo> cachedNovels1 = getNovelVoList(cachedNovels, key);
            if (cachedNovels1 != null) return cachedNovels1;
            List<Novel> novels = novelMapper.getRank();
            return convertToVo(novels);
        }
    }

    /**
     * 获取免费小说
     * @return
     */
    @Override
    public List<NovelVo> getFreeNovels() {
        //创建缓存key
        String key = RedisConstant.APP_FREE_NOVEL_PREFIX;
        //获取缓存
        List<NovelVo> cachedNovels = (List<NovelVo>) redisTemplate.opsForValue().get(key);
        if (!CollectionUtils.isEmpty(cachedNovels)) {
            return cachedNovels;
        }

        //查询数据库
        List<Novel> novels = novelMapper.getFreeNovels();
        cachedNovels = convertToVo(novels);

        //缓存写入
        long expireHours = CollectionUtils.isEmpty(cachedNovels) ? 1 : 12L;
        redisTemplate.opsForValue().set(key, Objects.requireNonNull(cachedNovels), expireHours, TimeUnit.HOURS);

        return cachedNovels;
    }

    /**
     * 获取评分最高的小说
     * @return
     */
    @Override
    public List<NovelVo> getTop2ScoreNovels() {
        //创建缓存key
        String key = RedisConstant.APP_TOP_2_SCORE_PREFIX;
        List<NovelVo> cachedNovels = (List<NovelVo>) redisTemplate.opsForValue().get(key);

        //缓存命中
        if (!CollectionUtils.isEmpty(cachedNovels)) {
            return cachedNovels;
        }

        //查询数据库
        List<Novel> novels = novelMapper.getTop2ScoreNovels();
        cachedNovels = convertToVo(novels);

        //缓存写入
        long expireHours = CollectionUtils.isEmpty(cachedNovels) ? 1 : 6L;
        redisTemplate.opsForValue().set(key, Objects.requireNonNull(cachedNovels), expireHours, TimeUnit.HOURS);

        return cachedNovels;
    }

    /**
     * 获取热门小说的
     * @param pageNum
     * @param pageSize
     * @return
     */
    @Override
    public List<NovelVo> getHotEnd(Integer pageNum, Integer pageSize) {
        //计算分页
        int start = calPage(pageNum, pageSize);

        //创建缓存key
        String key = RedisConstant.APP_HOT_END_PREFIX + ":" + pageNum + ":" + pageSize;
        List<NovelVo> cachedNovels = (List<NovelVo>) redisTemplate.opsForValue().get(key);

        //缓存命中
        if (!CollectionUtils.isEmpty(cachedNovels)) {
            return cachedNovels;
        }

        //查询数据库
        List<Novel> novels = novelMapper.getHotEnd(start, pageSize);
        cachedNovels = convertToVo(novels);

        //缓存写入
        long expireHours = CollectionUtils.isEmpty(cachedNovels) ? 1 : 8L;
        redisTemplate.opsForValue().set(key, Objects.requireNonNull(cachedNovels), expireHours, TimeUnit.HOURS);
        return cachedNovels;
    }

    /**
     * 获取今日必读
     * @return
     */
    @Override
    public List<NovelVo> getTodayMustRead() {
        //获取用户id
        Long userId = LoginUserHolder.getLoginUser().getUserId();

        //判断用户id是否 < 0
        if (userId < 0) {
            return getRank();
        }

        //根据用户id查询用户的偏好
        String tags = userMapper.getUserLikeType(userId);

        //判断数据是否为空
        if (tags == null || tags.isEmpty()) {
            return getRank();
        }

        //分割标签
        String[] tagArray = tags.split(",");

        //根据标签查询
        List<Novel> novels = novelMapper.getHotRank(tagArray, 0, 5);

        //进行对象的转换
        return convertToVo( novels);
    }

    /**
     * 获取新书
     * @param pageNum
     * @param pageSize
     * @return
     */
    @Override
    public List<NovelVo> getNewBooks(Integer pageNum, Integer pageSize) {
        //计算分页
        int start = calPage(pageNum, pageSize);

        //创建缓存key
        String key = RedisConstant.APP_NEW_BOOKS_PREFIX + ":" + start + ":" + pageSize;
        List<NovelVo> cachedNovels = (List<NovelVo>) redisTemplate.opsForValue().get(key);

        //缓存命中
        if (!CollectionUtils.isEmpty(cachedNovels)) {
            return cachedNovels;
        }

        //查询数据库
        List<Novel> novels = novelMapper.getNewBooks(start, pageSize);
        cachedNovels = convertToVo(novels);
        //缓存写入
        long expireHours = CollectionUtils.isEmpty(cachedNovels) ? 1 : 10L;
        redisTemplate.opsForValue().set(key, Objects.requireNonNull(cachedNovels), expireHours, TimeUnit.HOURS);
        return cachedNovels;

    }

    /**
     * 猜你喜欢
     * @param pageNum
     * @param pageSize
     * @return
     */
    @Override
    public List<NovelVo> getGuessLike(Integer pageNum, Integer pageSize) {
        // 1. 校验参数
        checkParam(pageNum, pageSize);

        // 2. 获取当前用户ID
        Long userId = LoginUserHolder.getLoginUser().getUserId();
        //判断用户id是否 < 0
        if (userId < 0) {
            return recommendHotNovels(pageNum, pageSize);
        }

        // 3. 获取当前用户的阅读历史
        List<History> userHistories = historyMapper.getHistoriesByUserId(0, 50, userId);
        if (userHistories.isEmpty()) {
            //根据用户id查询用户的偏好
            String tags = userMapper.getUserLikeType(userId);

            if (tags == null || tags.isEmpty()) {
                return recommendHotNovels(pageNum, pageSize);
            }

            //分割标签
            String[] tagArray = tags.split(",");

            //计算分页
            int start = calPage(pageNum,pageSize);

            //根据标签查询
            List<Novel> novels = novelMapper.getHotRank(tagArray,start,pageSize);
            return convertToVo(novels);
        }

        // 4. 提取当前用户阅读过的小说ID
        Set<Integer> userReadNovelIds = userHistories.stream()
                .map(History::getNovelId)
                .collect(Collectors.toSet());

        // 5. 获取有相似阅读行为的用户（与当前用户至少共同阅读过1本小说的用户）
        List<Long> similarUserIds = historyMapper.findSimilarUserIds(userReadNovelIds, userId);
        if (similarUserIds.isEmpty()) {
            // 没有相似用户时 fallback 到热门推荐
            return recommendHotNovels(pageNum, pageSize);
        }

        // 6. 计算用户相似度（基于余弦相似度）
        Map<Long, Double> userSimilarity = calculateUserSimilarity(userId, similarUserIds, userReadNovelIds);

        // 7. 获取相似用户喜欢的小说，并排除当前用户已读的
        List<Long> candidateNovelIds = historyMapper.findNovelsByUserIds(similarUserIds, userReadNovelIds);

        // 8. 计算小说推荐分数
        Map<Long, Double> novelScores = calculateNovelScores(candidateNovelIds, similarUserIds, userSimilarity);

        // 9. 按分数排序并分页
        List<Long> recommendedNovelIds = novelScores.entrySet().stream()
                .sorted(Map.Entry.<Long, Double>comparingByValue().reversed())
                .skip((long) (pageNum - 1) * pageSize)
                .limit(pageSize)
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());

        // 10. 转换为NovelVo返回
        List<Novel> novels = novelMapper.findNovelsByIds(recommendedNovelIds);
        return convertToVo(novels);
    }

    /**
     * 获取严选
     * @return
     */
    @Override
    public List<NovelVo> getNewBooksYanXuan() {
        //获取新书严选
        List<Novel> novels = novelMapper.getNewBooksYanXuan();
        return convertToVo(novels);
    }

    /**
     * 获取精品新书
     * @param pageNum
     * @param pageSize
     * @return
     */
    @Override
    public List<NovelVo> getJingPinNewBooks(Integer pageNum, Integer pageSize) {
        //计算分页
        int start = calPage(pageNum, pageSize);
        //获取精品新书
        List<Novel> novels = novelMapper.getJingPinNewBooks(start, pageSize);
        return convertToVo(novels);
    }

    /**
     * 获取免费小说
     * @param pageNum
     * @param pageSize
     * @return
     */
    @Override
    public List<NovelVo> getNewFreeNovels(Integer pageNum, Integer pageSize) {
        //计算分页
        int start = calPage(pageNum, pageSize);
        //查询
        List<Novel> novels = novelMapper.getNewFreeNovels(start, pageSize);
        return convertToVo(novels);
    }

    /**
     * 获取精品新书
     * @return
     */
    @Override
    public List<NovelVo> getJingXuanNewBooks() {
        //查询
        List<Novel> novels = novelMapper.getJingXuanNewBooks();
        //转换
        return convertToVo(novels);
    }

    /**
     * 获取免费小说排行
     * @param pageNum
     * @param pageSize
     * @return
     */
    @Override
    public List<NovelVo> getFreeRank(Integer pageNum, Integer pageSize) {
        //计算分页
        int start = calPage(pageNum, pageSize);

        //创建缓存key
        String key = RedisConstant.APP_FREE_RANK_PREFIX + ":" + start + ":" + pageSize;
        //从缓存里面获取数据
        List<NovelVo> cachedNovels = (List<NovelVo>) redisTemplate.opsForValue().get(key);
        if (cachedNovels != null) {
            return cachedNovels;
        }

        //缓存为空
        List<Novel> novels = novelMapper.getFreeRank(start, pageSize);
        cachedNovels = convertToVo(novels);

        //缓存写入
        long expireHours = CollectionUtils.isEmpty(cachedNovels) ? 1 : 10L;
        redisTemplate.opsForValue().set(key, cachedNovels, expireHours, TimeUnit.HOURS);
        return cachedNovels;
    }

    /**
     * 获取潜力新书
     * @param pageNum
     * @param pageSize
     * @return
     */
    @Override
    public List<NovelVo> getPotentialNewBooks(Integer pageNum, Integer pageSize) {
        //计算分页
        int start = calPage(pageNum, pageSize);
        //查询
        List<Novel> novels = novelMapper.getPotentialNewBooks(start, pageSize);
        return convertToVo(novels);
    }

    /**
     * 获取小说排行
     * @param pageNum
     * @param pageSize
     * @param type
     * @return
     */
    @Override
    public List<NovelVo> getRankByPage(Integer pageNum, Integer pageSize, String type) {
        // 1.计算分页
        int start = calPage(pageNum, pageSize);
        //创建缓存key
        String key = RedisConstant.APP_RANK_PREFIX + ":" + type + ":" + start + ":" + pageSize;
        //从缓存里面获取数据
        List<NovelVo> cachedNovels = (List<NovelVo>) redisTemplate.opsForValue().get(key);

        if (cachedNovels != null) {
            return cachedNovels;
        }

        //缓存为空
        cachedNovels = switch (type) {
            case "推荐榜" -> convertToVo(novelMapper.getHotNovelsPage(start, pageSize));
            case "人气榜" -> convertToVo(novelMapper.getPopularity(start, pageSize));
            case "高分榜" -> convertToVo(novelMapper.getPopularity(start, pageSize));
            case "完结榜" -> convertToVo(novelMapper.getHotEnd(start, pageSize));
            case "搜索榜" -> convertToVo(novelMapper.getSearch(start, pageSize));
            case "新书榜" -> convertToVo(novelMapper.getNewBooks(start, pageSize));
            default -> convertToVo(novelMapper.getHotNovelsPage(start, pageSize));
        };

        redisTemplate.opsForValue().set(key, cachedNovels, 10L, TimeUnit.HOURS);
        return cachedNovels;
    }

    /**
     * 获取作者
     * @param authorName
     * @param novelId
     * @return
     */
    @Override
    public List<NovelVo> getNovelsByAuthorName(String authorName, Integer novelId) {
        //获取作者的作品
        List<Novel> novels = novelMapper.getNovelsByAuthorName(authorName, novelId);
        //转换
        return convertToVo(novels);
    }

    /**
     * 获取标签
     *
     * @param tag
     * @param pageNum
     * @param size
     * @return
     */
    @Override
    public List<NovelVo> getNovelsByTagsPage(String tag, Integer pageNum, Integer size) {
        //计算分页
        int start = calPage(pageNum, size);
        //获取标签
        List<Novel> novels = novelMapper.getNovelsByTagsPage(tag, start, size);
        //转换
        return convertToVo(novels);
    }


    /**
     * 索引小说
     */
    @Override
    public void indexBooks() {
        List<Novel> novels = novelMapper.getAllNovel();
        try {
            LuceneUtils.createNovelIndex(novels);
        } catch (Exception e) {
            log.error("创建索引失败");
            throw new RuntimeException(e);
        }
    }

    /**
     * 搜索小说
     * @param keyword
     * @return
     */
    @Override
    public List<NovelVo> searchBook(String keyword) {
        try {
            //搜索小说
            List<NovelVo> novelVos = LuceneUtils.searchNovels(keyword);
            //添加搜索记录
            searchLogService.addSearchLog(keyword);
            return  novelVos;
        } catch (Exception e) {
            log.error("搜索小说失败");
            throw new RuntimeException(e);
        }
    }

    /**
     * 搜索小说建议
     * @param keyword
     * @return
     */
    @Override
    public List<String> searchSuggest(String keyword) {
        try {
            //获取小说搜索建议
            return LuceneUtils.getBookSearchSuggestions(keyword);
        } catch (Exception e) {
            log.error("获取小说搜索建议失败");
        }
        return Collections.emptyList();
    }

    /**
     * 获取小说推荐
     * @return
     */
    @Override
    public List<NovelVo> getHotReadRank() {
        //从redis里面获取数据
        List<Integer> topReadsNovels = getTopReadsNovels(9);
        Integer dff = 9 - topReadsNovels.size();
        if (dff > 0 && dff != 9) {
            //根据小说ID获取小说
            List<Novel> novels = novelMapper.selectNovelsByIds(topReadsNovels);
            List<Novel> ns = novelMapper.selectRecommendRank(0,dff);
            novels.addAll(ns);
            novels = novels.stream().distinct().toList();
            return convertToVo(novels);
        }else if(dff == 9)
        {
            List<Novel> ns = novelMapper.selectRecommendRank(0,9);
            return convertToVo(ns);
        } else if(dff == 0)
        {
            List<Novel> novels = novelMapper.selectNovelsByIds(topReadsNovels);
            return convertToVo(novels);
        }else {
            return Collections.emptyList();
        }
    }

    /**
     * 获取小说评论推荐
     * @return
     */
    @Override
    public List<NovelVo> getHotCommentRank() {
        //获取热门评论
        List<Novel> novels = novelMapper.selectCommentRank(0, 10);
        return convertToVo(novels);
    }


    /**
     * 获取阅读量前N的小说（从高到低）
     * @param topN 前N名
     * @return 小说ID+阅读量的集合（key=小说ID，value=阅读量）
     */
    public List<Integer> getTopReadsNovels(int topN) {
        // ZSet默认按分数升序，reverseRangeWithScores获取倒序（从高到低）
        Set<ZSetOperations.TypedTuple<Object>> tuples = redisTemplate.opsForZSet()
                .reverseRangeWithScores(RedisConstant.NOVEL_RANK_KEY, 0, topN - 1);

        List<Integer> result = new ArrayList<>();
        if (tuples != null) {
            for (ZSetOperations.TypedTuple<Object> tuple : tuples) {
                // 小说ID（强转Integer）
                Integer novelId = (Integer) tuple.getValue();
                // 阅读量（分数）
                Double reads = tuple.getScore();
                result.add(novelId);
            }
        }
        return result;
    }


    /**
     * @param novelId
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateNovelReads(Integer novelId) {
        //更新热门小说
        novelMapper.updateNovelReads(novelId);
        // 2. 同步更新Redis ZSet：该小说的分数（阅读量）+1
        // 若该小说ID不存在于ZSet中，会自动添加，初始分数为1
        redisTemplate.opsForZSet().incrementScore(RedisConstant.NOVEL_RANK_KEY, novelId, 1);
    }

    /**
     * 更新小说搜索次数
     * @param novelId
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateNovelSearchCount(Integer novelId) {
        //创建换成key
        String key = RedisConstant.NOVEL_SEARCH_COUNT_KEY + novelId;
        //使用zSet实现热搜排行榜
        redisTemplate.opsForZSet().incrementScore(key, novelId, 1);
        //更新搜索次数
        novelMapper.updateNovelSearchCount(novelId);
    }

    /**
     * 获取用户喜欢小说
     * @return
     */
    @Override
    public NovelVo getUserLikeNovel() {
        //获取用户id
        Long userId = getCurrentUserId();

        //根据用户id查询浏览历史
        List<History> histories = historyMapper.getHistoriesByUserId(0, 50, userId);

        //收集小说ID
        List<Integer> novelIds = histories.stream()
                .map(History::getNovelId)
                .toList();

        //根据小说ID获取小说
        List<Novel> novels = novelMapper.selectNovelsByIds(novelIds);

        // 1. 先按分类分组
        Map<String, List<Novel>> groupedNovels = novels.stream()
                .collect(Collectors.groupingBy(Novel::getCategoryName));

        // 2. 获取数量最多的分组
        List<Novel> maxCountNovels = novels.stream()
                .collect(Collectors.groupingBy(Novel::getCategoryName))
                .entrySet().stream()
                // 找到数量最多的分组条目
                .max(Comparator.comparingInt(entry -> entry.getValue().size()))
                // 若没有任何小说，返回空列表（避免空指针）
                .map(Map.Entry::getValue)
                .orElse(Collections.emptyList());

        // 3. 提取所有小说的标签（处理逗号分隔的字符串），并统计每个标签的出现次数
        Map<String, Long> tagCountMap = maxCountNovels.stream()
                // 1.1 先获取每个小说的标签字符串（处理null情况，避免空指针）
                .map(novel -> {
                    String tagsStr = novel.getTags();
                    return tagsStr != null ? tagsStr : ""; // 若标签为null，视为空字符串
                })
                // 1.2 按逗号拆分标签字符串
                .map(tagsStr -> tagsStr.split(","))
                // 1.3 将数组转为流（Stream<String>），扁平化处理
                .flatMap(Arrays::stream)
                // 1.4 清洗标签：去掉前后空格，过滤空标签（例如拆分后可能有 "" 或 "  "）
                .map(String::trim)
                .filter(tag -> !tag.isEmpty()) // 只保留非空标签
                // 1.5 统计每个标签的出现次数
                .collect(Collectors.groupingBy(
                        Function.identity(),  // 以标签本身为键
                        Collectors.counting()  // 统计次数
                ));

        // 4. 找到出现次数最多的标签（处理并列情况）
        long maxTagCount = tagCountMap.values().stream()
                .max(Long::compare)
                .orElse(0L);

        List<String> mostFrequentTags = tagCountMap.entrySet().stream()
                .filter(entry -> entry.getValue().equals(maxTagCount))
                .map(Map.Entry::getKey)
                .toList();

        //5.根据标签和小说ids获取最喜欢的小说
        Novel novel = novelMapper.selectNovelsByTags(mostFrequentTags, novelIds);
        //转换为集合
        List<Novel> novelList = new ArrayList<>();
        novelList.add(novel);
        //返回对象
        return convertToVo(novelList).get(0);
    }


    /**
     * 计算小说推荐分数
     */
    @NotNull
    private Map<Long, Double> calculateNovelScores(@NotNull List<Long> candidateNovelIds,
                                                   List<Long> similarUserIds,
                                                   Map<Long, Double> userSimilarity) {
        Map<Long, Double> novelScores = new HashMap<>();

        // 为每个候选小说计算推荐分数
        for (Long novelId : candidateNovelIds) {
            double score = 0.0;

            // 找到所有阅读过该小说的相似用户
            List<Long> usersWhoReadThisNovel = historyMapper.findUsersByNovelId(novelId);
            usersWhoReadThisNovel.retainAll(similarUserIds);

            // 计算加权分数（相似度作为权重）
            for (Long userId : usersWhoReadThisNovel) {
                Double similarity = userSimilarity.get(userId);
                if (similarity != null) {
                    // 可以根据阅读次数或阅读时长调整分数
                    int readCount = historyMapper.countReadsByUserAndNovel(userId, novelId);
                    score += similarity * readCount;
                }
            }

            if (score > 0) {
                novelScores.put(novelId, score);
            }
        }

        return novelScores;
    }


    /**
     * 计算用户相似度（余弦相似度）
     */
    @NotNull
    private Map<Long, Double> calculateUserSimilarity(Long targetUserId, @NotNull List<Long> similarUserIds, Set<Integer> targetUserNovels) {
        Map<Long, Double> similarityMap = new HashMap<>();

        for (Long userId : similarUserIds) {
            // 获取该用户阅读过的小说
            Set<Integer> userNovels = historyMapper.getNovelIdsByUserId(userId);

            // 计算交集大小
            long intersection = userNovels.stream()
                    .filter(targetUserNovels::contains)
                    .count();

            // 计算余弦相似度: cosθ = 交集大小 / (√A * √B)
            double similarity = 0;
            if (intersection > 0) {
                double denominator = Math.sqrt(targetUserNovels.size()) * Math.sqrt(userNovels.size());
                similarity = intersection / denominator;
            }

            if (similarity > 0) {
                similarityMap.put(userId, similarity);
            }
        }

        return similarityMap;
    }

    /**
     * 校验参数
     * @param pageNum
     * @param pageSize
     */
    private void checkParam(Integer pageNum, Integer pageSize) {
        if (pageNum == null || pageNum < 1) {
            pageNum = 1;
        }
        if (pageSize == null || pageSize < 1 || pageSize > 100) { // 限制最大页大小，防止查询过大
            pageSize = 15;
        }
    }


    /**
     * 分页获取热门数据
     * @param pageNum
     * @param pageSize
     * @return
     */
    @Nullable
    private List<NovelVo> recommendHotNovels(Integer pageNum, Integer pageSize) {
        //计算分页
        Integer start = (pageNum - 1) * pageSize;
        //分页获取热门数据
        List<Novel> novels = novelMapper.getHotNovelsPage(start, pageSize);
        if(novels.isEmpty())
        {
            return Collections.emptyList();
        }
        //进行对象转换
        return convertToVo(novels);
    }



    /**
     * 计算分页
     * @param pageNum
     * @param pageSize
     * @return
     */
    private int calPage(Integer pageNum, Integer pageSize) {
        //校验参数
        if (pageNum == null || pageNum < 1 || pageSize == null || pageSize < 1) {
            pageNum = 1;
            pageSize = 10;
        }
        //计算起始索引
        return (pageNum - 1) * pageSize;
    }

    @Nullable
    private List<NovelVo> getNovelVoList(List<NovelVo> cachedNovels, String key) {
        // 循环重试
        int maxRetry = 3;
        int retryCount = 0;
        while (retryCount < maxRetry) {
            retryCount++;
            try {
                // 指数退避策略（重试间隔递增，减少无效请求）
                long sleepMs = 50 * (1 << retryCount); // 50ms → 100ms → 200ms
                Thread.sleep(sleepMs);
                cachedNovels = (List<NovelVo>) redisTemplate.opsForValue().get(key);
                if (!CollectionUtils.isEmpty(cachedNovels)) {
                    return cachedNovels;
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // 保留中断状态
                break; // 中断后退出重试
            }
        }
        return null;
    }


    /**
     * 转换为VO（优化：使用MapStruct替代BeanUtils，提升性能）
     */
    @Nullable
    private List<NovelVo> convertToVo(List<Novel> novels) {
        if (CollectionUtils.isEmpty(novels)) {
            return Collections.emptyList();
        }
        //使用MapStruct映射（编译时生成代码，性能优于BeanUtils）
        return novels.stream()
                .map(novel -> {
                    NovelVo vo = new NovelVo();
                    BeanUtils.copyProperties(novel, vo);
                    vo.setStatus(NovelStatus.getNameByCode(novel.getStatus()));
                    //获取小说的标签
                    String[] tags = novel.getTags().split(",");
                    vo.setTags(Arrays.asList(tags));
                    return vo;
                })
                .collect(Collectors.toList());
    }

    /**
     * 获取当前用户ID
     * @return
     */
    public Long getCurrentUserId() {
        //获取当前用户ID
        LoginUser loginUser = LoginUserHolder.getLoginUser();
        if (loginUser == null) {
            throw new BaseException(ResultCodeEnum.ILLEGAL_REQUEST);
        }
        return loginUser.getUserId();
    }
}
